/**
 * Viewer
 */

import * as THREE from '../threejs/build/three.module.js';
import { MapControls } from '../threejs/js/controls/OrbitControls.js';
import { GLTFLoader } from '../threejs/js/loaders/GLTFLoader.js';
import { environments } from '../threejs/assets/environment/index.js';
import { RGBELoader } from '../threejs/js/loaders/RGBELoader.js';
import { CSS2DRenderer } from '../threejs/js/renderers/CSS2DRenderer.js';

import { createMsg } from '../js/Msg.js';

class Viewer {
    constructor(near, far, cameraPosition, cameraTarget) {
        this.el = document.querySelector("#threeContainer");

        this.el.onselectstart = function () {
            return false;
        };

        this.near = near;
        this.far = far;
        this.cameraPosition = cameraPosition;
        this.cameraTarget = cameraTarget;

        this.scene = new THREE.Scene();

        this.camera = new THREE.PerspectiveCamera(60, this.el.clientWidth / this.el.clientHeight, near, far);
        this.scene.add(this.camera);

        this.renderer = new THREE.WebGLRenderer({ antialias: true });
        this.renderer.domElement.id = 'threeCanvas';
        this.renderer.physicallyCorrectLights = true;
        this.renderer.outputEncoding = THREE.sRGBEncoding;
        this.renderer.setClearColor(0x002b4b);
        this.renderer.setPixelRatio(window.devicePixelRatio);
        this.renderer.setSize(this.el.clientWidth, this.el.clientHeight);
        this.renderer.domElement.style.outline = 'none';

        this.pmremGenerator = new THREE.PMREMGenerator(this.renderer);
        this.pmremGenerator.compileEquirectangularShader();

        this.controls = new MapControls(this.camera, this.renderer.domElement);
        this.controls.autoRotate = false;
        this.controls.autoRotateSpeed = -10;

        this.el.appendChild(this.renderer.domElement);

        this.addLights(); //添加灯光

        this.updateEnvironment(); //真实环境光

        this.setSkyBox();

        window.addEventListener('resize', this.resize.bind(this), false);

        this.camera.position.set(...this.cameraPosition);
        this.controls.target.set(...this.cameraTarget);
        this.controls.update();

        //标签renderer
        this.labelRenderer = new CSS2DRenderer();
        this.labelRenderer.setSize(window.innerWidth, window.innerHeight);
        this.labelRenderer.domElement.style.position = 'absolute';
        this.labelRenderer.domElement.style.top = 0;
        this.labelRenderer.domElement.style.left = 0;
        this.labelRenderer.domElement.style.pointerEvents = 'none';

        this.el.appendChild(this.labelRenderer.domElement);
    }

    addLights() {
        let ambientLight = new THREE.AmbientLight(0xFFFFFF, 0.5); //环境光
        this.camera.add(ambientLight);

        let directionalLight = new THREE.DirectionalLight(0xFFFFFF, 1); //平行光
        directionalLight.position.set(0.5, 0, 0.866); // ~60º
        this.camera.add(directionalLight);
    }

    resize() {
        const { clientHeight, clientWidth } = this.el.parentElement;

        this.camera.aspect = clientWidth / clientHeight;
        this.camera.updateProjectionMatrix();
        this.renderer.setSize(clientWidth, clientHeight);
    }

    loadGltf(url, callback) {

        let onProgress = function (xhr) {
            if (xhr.lengthComputable) {
                let percentComplete = xhr.loaded / xhr.total * 100;
                console.log(Math.round(percentComplete, 2) + '% downloaded');
            }
        };

        let onError = function (xhr) {
            $('.load-container').hide();
            createMsg().alert('模型 ' + url + ' 加载失败');
        };

        let loader = new GLTFLoader();

        loader.load(url, gltf => {
            const model = gltf.scene || gltf.scenes[0];
            const clips = gltf.animations || [];

            this.scene.add(model);

            callback && callback(gltf);

        }, onProgress, onError);
    }

    //真实环境光
    updateEnvironment() {
        const environment = environments.filter((entry) => entry.name === environments[1].name)[0];

        this.getCubeMapTexture(environment).then(({ envMap }) => {
            this.scene.environment = envMap;
        });
    }

    getCubeMapTexture(environment) {
        const { path } = environment;

        // no envmap
        if (!path) return Promise.resolve({ envMap: null });

        return new Promise((resolve, reject) => {
            new RGBELoader()
                .setDataType(THREE.UnsignedByteType)
                .load(path, (texture) => {

                    const envMap = this.pmremGenerator.fromEquirectangular(texture).texture;
                    this.pmremGenerator.dispose();

                    resolve({ envMap });
                }, undefined, reject);
        });
    }

    //天空盒
    setSkyBox() {
        let urls = [
            './images/sky/2/px.png',
            './images/sky/2/nx.png',
            './images/sky/2/py.png',
            './images/sky/2/ny.png',
            './images/sky/2/pz.png',
            './images/sky/2/nz.png',
        ];

        // let urls = [
        //     './images/sky/1/px.jpg',
        //     './images/sky/1/nx.jpg',
        //     './images/sky/1/py.jpg',
        //     './images/sky/1/ny.jpg',
        //     './images/sky/1/pz.jpg',
        //     './images/sky/1/nz.jpg',
        // ];

        let cubeLoader = new THREE.CubeTextureLoader();
        this.scene.background = cubeLoader.load(urls);

        // 将平面添加到场景中
        let plane = this.createPlaneGeometryBasicMaterial();
        this.scene.add(plane);
    }

    /**
     * 创建地面并添加材质
     * wrapS属性定义的是纹理沿x轴方向的行为，而warpT属性定义的是纹理沿y轴方向的行为。
     * Three.js为这些属性提供了如下两个选项：
     * THREE.RepeatWrapping允许纹理重复自己。
     * THREE.ClampToEdgeWrapping是属性的默认值。
     * 属性值为THREE.ClampToEdgeWrapping时，那么纹理的整体不会重复，只会重复纹理边缘的像素来填满剩下的空间。
     */
    createPlaneGeometryBasicMaterial() {
        let textureLoader = new THREE.TextureLoader();
        let cubeMaterial = new THREE.MeshStandardMaterial({
            map: textureLoader.load("./images/sky/cd.png"),
        });
        cubeMaterial.map.wrapS = THREE.RepeatWrapping;
        cubeMaterial.map.wrapT = THREE.RepeatWrapping;
        cubeMaterial.map.repeat.set(30, 30);
        cubeMaterial.side = THREE.DoubleSide;
        // 创建地平面并设置大小
        let planeGeometry = new THREE.PlaneGeometry(30000, 30000);
        //let planeGeometry = new THREE.PlaneGeometry(1000, 1000);
        let plane = new THREE.Mesh(planeGeometry, cubeMaterial);

        // 设置平面位置并旋转
        plane.rotation.x = -0.5 * Math.PI;
        plane.position.x = 0;
        plane.position.y = -300;
        plane.position.z = 0;
        return plane;
    }
}

export { Viewer }
